#ifndef CHAINSAW_COMMON_PRINT_H
#define CHAINSAW_COMMON_PRINT_H

#include <string>
#include <mutex>
#include <vector>
#include <fstream>
#include <thread>
#include <sstream>
#include <utility>
#include <iostream>
#include <array>

namespace chainsaw
{
    class SafeLogger
    {
    public:
    enum class LogLevel
    {
        INFO,
        WARN,
        ERROR
    };

    public:
        static SafeLogger &instance();

        void setLogFile(std::string const &filename);

        void printTime(bool enable);

        void printIdMessage(bool enable);

        void printChainLabel(bool enable);

        template <typename... Args>
        void log(LogLevel level, Args &&...args)
        {
            std::lock_guard<std::mutex> lock(m_mutex);

            std::ostringstream oss;
            if (m_flags.print_chain)
                oss << m_head << ' ' << m_log_separator[0] << m_log_separator[1] << ' ';
            if (m_flags.print_time)
                oss << getTimeStamp();
            if (m_flags.print_id_msg)
                oss << " [T" << std::this_thread::get_id() << "] ";
            oss << "[" << levelToColorString(level) << "\033[0m] ";
            (oss << ... << args);

            std::string line = oss.str();

            //
            std::cout << line;
            if (m_allowWrap)
                std::cout << std::endl;

            //
            if (m_log_file.is_open())
            {
                m_log_file << stripAnsiCodes(line);
                if (m_allowWrap)
                    m_log_file << std::endl;
            }
        }

        template <typename... Args>
        void info(Args &&...args) { log(LogLevel::INFO, std::forward<Args>(args)...); }

        template <typename... Args>
        void warn(Args &&...args) { log(LogLevel::WARN, std::forward<Args>(args)...); }

        template <typename... Args>
        void error(Args &&...args) { log(LogLevel::ERROR, std::forward<Args>(args)...); }

        template <typename... Args>
        void info_nowrap(Args &&...args) { nowrap(LogLevel::INFO, std::forward<Args>(args)...); }

        template <typename... Args>
        void error_nowrap(Args &&...args) { nowrap(LogLevel::ERROR, std::forward<Args>(args)...); }

        template <typename... Args>
        void log_raw(Args &&...args)
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            std::ostringstream oss;
            (oss << ... << std::forward<Args>(args));
            std::string line = oss.str();
            std::cout << line;
            if (m_log_file.is_open())
                m_log_file << line;
        }

        void log_endl()
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            std::cout << std::endl;
            if (m_log_file.is_open())
                m_log_file << std::endl;
        }

        template <typename T>
        void log_message(T const &value)
        {
            info_nowrap();
            log_obj(value);
            log_endl();
        }

        template <typename T>
        void log_message(T value, std::string tip)
        {
            if (tip.empty())
            {
                log_message(value);
            }
            else
            {
                info_nowrap(tip, m_tip_symbol);
                log_obj(value);
                log_endl();
            }
        }

        template <typename T>
        void log_message(std::vector<T> value, std::string tip)
        {
            if (!tip.empty())
            {
                m_log_separator[0] = 'L';
                log(LogLevel::INFO, tip, m_tip_symbol, '{', value.size(), '}');
            }

            m_log_separator[0] = '-';
            m_log_separator[1] = 'l';

            for (T const &t : value)
            {
                info_nowrap();
                log_obj(t);
                log_endl();
            }

            reset_headtype();
        }

    private:
        SafeLogger() = default;

        ~SafeLogger();

        SafeLogger(const SafeLogger &) = delete;
        SafeLogger &operator=(const SafeLogger &) = delete;

        std::string getTimeStamp() const;

        std::string levelToColorString(LogLevel level) const;

        std::string stripAnsiCodes(const std::string &s) const;

        void judge_headtype(std::string value);

        void reset_headtype();

        template <typename... Args>
        void nowrap(LogLevel level, Args &&...args)
        {
            m_allowWrap = false;
            log(level, std::forward<Args>(args)...);
            m_allowWrap = true;
        }

        template <typename T>
        void log_obj(T const &args)
        {
            log_raw(args);
        }

    private:
        mutable std::mutex m_mutex;
        std::ofstream m_log_file;
        const std::string m_head = "[ chainsaw ]";
        std::array<char, 2> m_log_separator = {'-', '-'};

        std::string m_tip_symbol = ":";
        bool m_allowWrap = true;

        struct extend_print_flag
        {
            bool print_time = false;
            bool print_id_msg = false;
            bool print_chain = true;
        } m_flags;
    };

    template <>
    void SafeLogger::log_message(std::string const &value);

    template <>
    void SafeLogger::log_obj(std::string const &args);

    template <>
    void SafeLogger::log_obj(bool const &args);

    template <>
    void SafeLogger::log_obj(char const &args);
}
// namespace chainsaw

namespace chainsaw
{
    template <typename... Args>
    void print_class_error(std::string class_msg, Args &&...args)
    {
        SafeLogger::instance().error_nowrap("[chainsaw::", class_msg, "]->");
        SafeLogger::instance().log_raw(std::forward<Args>(args)...);
        SafeLogger::instance().log_endl();
    }

    template <typename... Args>
    void info(Args &&...args) { SafeLogger::instance().info(std::forward<Args>(args)...); }

    template <typename... Args>
    void warn(Args &&...args) { SafeLogger::instance().warn(std::forward<Args>(args)...); }

    template <typename... Args>
    void error(Args &&...args) { SafeLogger::instance().error(std::forward<Args>(args)...); }

    template <typename... Args>
    void info_nowrap(Args &&...args) { SafeLogger::instance().info_nowrap(std::forward<Args>(args)...); }

    template <typename... Args>
    void error_nowrap(Args &&...args) { SafeLogger::instance().error_nowrap(std::forward<Args>(args)...); }

    template <typename... Args>
    void raw(Args &&...args) { SafeLogger::instance().log_raw(std::forward<Args>(args)...); }

    inline void raw_endl() { SafeLogger::instance().log_endl(); }

    template <typename T>
    void message(T t) { SafeLogger::instance().log_message(t); }

    template <typename T>
    void message(T t, std::string tip) { SafeLogger::instance().log_message(t, tip); }

} // namespace chainsaw

#ifndef PR
#define PR(data) chainsaw::message(std::string(#data))
#endif // !PR

#ifndef PRP
#define PRP(data) chainsaw::message(data, #data)
#endif // !PRP

#ifndef PRP2
#define PRP2(data, tips) chainsaw::message(data, #tips)
#endif // !PRP2

#ifndef OPEN_LOG_FILE
#define OPEN_LOG_FILE(file) chainsaw::SafeLogger::instance().setLogFile(file)
#endif // !OPEN_LOG_FILE

#ifndef OPEN_LOG_TIME
#define OPEN_LOG_TIME chainsaw::SafeLogger::instance().printTime(true)
#endif // !OPEN_LOG_TIME

#ifndef OPEN_LOG_TID
#define OPEN_LOG_TID chainsaw::SafeLogger::instance().printIdMessage(true)
#endif // !OPEN_LOG_TID

#ifndef CLOSE_PRINT_CHAIN
#define CLOSE_PRINT_CHAIN chainsaw::SafeLogger::instance().printChainLabel(false)
#endif // !CLOSE_PRINT_CHAIN

#ifndef OPEN_PRINT_CHAIN
#define OPEN_PRINT_CHAIN chainsaw::SafeLogger::instance().printChainLabel(true)
#endif // !OPEN_PRINT_CHAIN

#endif // !CHAINSAW_COMMON_PRINT_H
